﻿using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.UI;

[RequireComponent(typeof(Camera))]
public class LX_DistortManager : MonoBehaviour
{
    //用Dictionary来管理场景中需要做扭曲效果的物体，通过挂在物体身上的脚本来添加或者移除
    private Dictionary<Renderer, Material> rendererDict = new Dictionary<Renderer, Material>();
    private const string commandBufferName = "DistortCommandBuffer";
    private const string screenColorName = "_ScreenColorTexture";
    private const string screenDepthName = "_ScreenDepthTexture";

    //CameraEvent用来指定CommandBuffer在什么时候执行
    private CameraEvent cameraEvent = CameraEvent.AfterImageEffects;
    //RT的分辨率，也会影响到最终输出到屏幕的分辨率
    public int DownSample = 0;
    //显示相机原本画面的Quad绘制距离，要在所有扭曲区域之后
    public float ShowMeshDistance = 100;
    private CommandBuffer commandBuffer;
    private Camera renderCam;
    private int screenColorID = -1;
    private int screenDepthID = -1;
    private Resolution currentResolution = new Resolution();
    private RenderTexture screenColorRT;
    private RenderTexture screenDepthRT;
    //用来显示相机原本画面的Quad
    private Mesh camShowMesh;
    private Material camShowMaterial;

    private static LX_DistortManager _distortManager;
    public static LX_DistortManager GetInstance
    {
        get
        {
            if (!_distortManager)
            {
                _distortManager = GameObject.FindObjectOfType<LX_DistortManager>();
            }
            return _distortManager;
        }
    }

    private Camera GetCamera
    {
        get
        {
            if (!renderCam)
            {
                renderCam = GetComponent<Camera>();
            }
            return renderCam;
        }
    }


    private void OnEnable()
    {

        Initialize();
        SetResolution();

        CreateCommandBuffer();
    }

    private void OnPreRender()
    {
        //当Dictionary里面有需要扭曲的物体时才执行，如果场景中没有需要扭曲的物体就会继续按照原来的方式渲染
        if (rendererDict.Count > 0)
        {
            //当屏幕分辨率发生变化是更新显示
            if (currentResolution.width != renderCam.pixelWidth || currentResolution.height != renderCam.pixelHeight)
            {
                SetResolution();
                CreateCommandBuffer();
            }
            //设置相机渲染目标的ColorBuffer和DepthBuffer分别设置到两张RenderTexture上
            renderCam.SetTargetBuffers(screenColorRT.colorBuffer, screenDepthRT.depthBuffer);
        }
    }

    private void OnPostRender()
    {
        if (rendererDict.Count > 0)
            renderCam.targetTexture = null;
    }

    private void OnDestroy()
    {
        //删除CommandBuffer
        DestroyCommandBuffer();
        //释放申请的两张RenderTexture
        RenderTexture.ReleaseTemporary(screenColorRT);
        RenderTexture.ReleaseTemporary(screenDepthRT);
        //清空Dictionary
        rendererDict.Clear();
        //删除QuadMesh
        Destroy(camShowMesh);
    }

    private void CreateCommandBuffer()
    {

        DestroyCommandBuffer();

        if (rendererDict.Count > 0)
        {
            RenderTexture.ReleaseTemporary(screenColorRT);
            RenderTexture.ReleaseTemporary(screenDepthRT);
            //申请两张RT，存储ColorBuffer的不需要深度值所以Depth值为0，存储DepthBuffer的格式为RenderTextureFormat.Depth
            screenColorRT = RenderTexture.GetTemporary(renderCam.pixelWidth >> DownSample, renderCam.pixelHeight >> DownSample, 0, RenderTextureFormat.Default);
            screenDepthRT = RenderTexture.GetTemporary(renderCam.pixelWidth >> DownSample, renderCam.pixelHeight >> DownSample, 16, RenderTextureFormat.Depth);
            screenColorRT.name = "CopyColorTempRT";
            screenDepthRT.name = "CopyDepthTempRT";

            commandBuffer = new CommandBuffer();
            commandBuffer.name = commandBufferName;
            commandBuffer.Clear();

            //清除之前缓冲区中的Color和Depth
            commandBuffer.ClearRenderTarget(true, true, Color.black);
            //将存储了Color和Depth的两张RT传递给shader
            commandBuffer.SetGlobalTexture(screenColorID, screenColorRT);
            commandBuffer.SetGlobalTexture(screenDepthID, screenDepthRT);
            //绘制一个Quad来显示相机原本渲染的画面
            camShowMesh = CreateCamShowMesh(ShowMeshDistance);
            commandBuffer.DrawMesh(camShowMesh, Matrix4x4.identity, camShowMaterial);
            //遍历Dictionary将场景中需要扭曲的物体绘制出来
            foreach (var dict in rendererDict)
            {
                commandBuffer.DrawRenderer(dict.Key, dict.Value);
            }
            renderCam.AddCommandBuffer(cameraEvent, commandBuffer);
        }
    }

    private void SetResolution()
    {
        currentResolution.width = renderCam.pixelWidth;
        currentResolution.height = renderCam.pixelHeight;
    }

    private void DestroyCommandBuffer()
    {
        if (commandBuffer != null)
        {
            GetCamera.RemoveCommandBuffer(cameraEvent, commandBuffer);
            commandBuffer.Clear();
            commandBuffer = null;
        }
    }

    private void Initialize()
    {
        if (!renderCam)
        {
            renderCam = GetComponent<Camera>();
        }

        if (screenColorID == -1)
        {
            screenColorID = Shader.PropertyToID(screenColorName);
        }
        if (screenDepthID == -1)
        {
            screenDepthID = Shader.PropertyToID(screenDepthName);
        }
        if (camShowMaterial == null)
        {
            camShowMaterial = new Material(Shader.Find("Custom/Common/LX_CommandBufferTex"));
        }
    }
    //往Dictionary中添加需要扭曲的物体
    public void AddRenderer(Renderer renderer, Material material)
    {
        rendererDict.Add(renderer, material);
        CreateCommandBuffer();
        //Debug.Log(rendererDict.Count);
    }
    //从Dictionary中移除不需要扭曲的物体
    public void RemoveRenderer(Renderer renderer)
    {
        rendererDict.Remove(renderer);
        CreateCommandBuffer();
        //Debug.Log(rendererDict.Count);
    }
    //通过屏幕四个点和distance在场景中绘制一个Quad
    private Mesh CreateCamShowMesh(float distance)
    {
        Vector3[] vertices = new Vector3[4];
        vertices[0] = renderCam.ScreenToWorldPoint(new Vector3(0, 0, distance));
        vertices[1] = renderCam.ScreenToWorldPoint(new Vector3(0, renderCam.pixelHeight, distance));
        vertices[2] = renderCam.ScreenToWorldPoint(new Vector3(renderCam.pixelWidth, renderCam.pixelHeight, distance));
        vertices[3] = renderCam.ScreenToWorldPoint(new Vector3(renderCam.pixelWidth, 0, distance));

        Vector2[] uvs = new Vector2[4];
        uvs[0] = new Vector2(0, 0);
        uvs[1] = new Vector2(0, 1);
        uvs[2] = new Vector2(1, 1);
        uvs[3] = new Vector2(1, 0);

        int[] triangleID = new int[6];
        triangleID[0] = 0;
        triangleID[1] = 1;
        triangleID[2] = 2;
        triangleID[3] = 2;
        triangleID[4] = 3;
        triangleID[5] = 0;

        Mesh drawMesh = new Mesh();
        drawMesh.vertices = vertices;
        drawMesh.uv = uvs;
        drawMesh.triangles = triangleID;

        return drawMesh;
    }
}